import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.opengauss.util.GT;

/**
 * @作者 ： pwx5328113 @时间： 2024/1/25 @方法说明： 入参： 1."IP:port" 2.database name 3.
 *     prefix table name 4.username 5.password 6.Sub-transaction number
 *     7.timeBeforeRollback(s) 8.commit connection number 9.rollback connection
 *     number
 **/

public class Human_Subxact_Rollback_Case0001 {
	private static String SCHEMANAME = "test";
	private static String URLstr;
	private static String USERNAME;
	private static String PASSWORD;
	private static int COMMIT_CONNECTIONS = 1000;
	private static int ROLLBACK_CONNECTIONS = 1000;
	private static int INSERT_NUM = 200;
	private static long startTimeStamp;
	private static long timeBeforeRollback = 1000 * 90;

	private static void initTables() throws SQLException {
		System.out.println(URLstr);
		Statement statement;
		try (Connection connection = DriverManager.getConnection(URLstr, USERNAME, PASSWORD)) {
			statement = connection.createStatement();
			statement.execute("drop schema if exists " + SCHEMANAME + " CASCADE");
			statement.execute("create schema " + SCHEMANAME);
			for (int i = 0; i < ROLLBACK_CONNECTIONS; i++) {
				statement.execute(
						"create table if not exists " + SCHEMANAME + ".t_rollback" + i + "(id int, value varchar)");
			}
			for (int i = 0; i < COMMIT_CONNECTIONS; i++) {
				statement.execute(
						"create table if not exists " + SCHEMANAME + ".t_commit" + i + "(id int, value varchar)");
			}
			statement.close();
		}
		System.out.println(GT.tr("init tables complete, commit connections: {0}, " + "rollback connections: {1}",
				COMMIT_CONNECTIONS, ROLLBACK_CONNECTIONS));
	}

	private static void executeInsertAndSavepoint(Connection connection, String tableName, int insertNum)
			throws SQLException {
		PreparedStatement preparedStatement = null;
		for (int i = 0; i < insertNum; i++) {
			try {
				connection.setSavepoint(GT.tr("savepoint{0}_{1}", tableName, String.valueOf(i)));
				preparedStatement = connection
						.prepareStatement(GT.tr("insert into " + SCHEMANAME + ".{0} values (?,?)", tableName));
				preparedStatement.setInt(1, i);
				preparedStatement.setString(2, "testtesttest");
				preparedStatement.execute();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally {
				assert preparedStatement != null;
				preparedStatement.close();
			}
		}
	}

	private static void initTable(Connection connection, String tableName) throws SQLException {
		Statement statement = null;
		try {
			statement = connection.createStatement();
			statement.execute("create table if not exists " + SCHEMANAME + "." + tableName + "(id int, value varchar)");
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			assert statement != null;
			statement.close();
		}
	}

	private static void executeCommitConnections(int tableId) throws SQLException {
		Connection connection = DriverManager.getConnection(URLstr, USERNAME, PASSWORD);
		try {
			connection.setAutoCommit(false);
			initTable(connection, GT.tr("t_commit{0}", String.valueOf(tableId)));
			executeInsertAndSavepoint(connection, GT.tr("t_commit{0}", String.valueOf(tableId)), INSERT_NUM);
			long endTimeStamp = System.currentTimeMillis();
			long sleepTimeMillSeconds = timeBeforeRollback - endTimeStamp + startTimeStamp;
			System.out.println(
					GT.tr("CommitConnection finish executeInsertAndSavepoint, start to sleep, id: {0}, sleepTimeMillSeconds: {1}",
							String.valueOf(tableId), sleepTimeMillSeconds));
			Thread.sleep(sleepTimeMillSeconds);

			long timeBeforeCommit = System.currentTimeMillis();
			connection.commit();
			long timeAfterCommit = System.currentTimeMillis();
			System.out
					.println(GT.tr("Transaction commit,id: {0}, date: {1}, time interval: {2}", String.valueOf(tableId),
							DatetimeUtil.getTheTimeInMilliseconds(), timeAfterCommit - timeBeforeCommit));
		} catch (SQLException | InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private static void executeRollbackConnections(int tableId) throws SQLException {
		Connection connection = DriverManager.getConnection(URLstr, USERNAME, PASSWORD);
		try {
			connection.setAutoCommit(false);
			initTable(connection, GT.tr("t_rollback{0}", String.valueOf(tableId)));
			executeInsertAndSavepoint(connection, GT.tr("t_rollback{0}", String.valueOf(tableId)), INSERT_NUM);
			long endTimeStamp = System.currentTimeMillis();
			long sleepTimeMillSeconds = timeBeforeRollback - endTimeStamp + startTimeStamp;
			System.out.println(
					GT.tr("RollbackConnection finish executeInsertAndSavepoint, start to sleep, id: {0}, sleepTimeMillSeconds: {1}",
							String.valueOf(tableId), sleepTimeMillSeconds));
			Thread.sleep(sleepTimeMillSeconds);

			long startTime = System.currentTimeMillis();
			connection.rollback();
			long endTime = System.currentTimeMillis();
			System.out.println(GT.tr("Transaction rollback,id: {0}, date: {1}, time interval: {2}",
					String.valueOf(tableId), DatetimeUtil.getTheTimeInMilliseconds(), endTime - startTime));
		} catch (SQLException | InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private static void execute() {
		ExecutorService executorService = Executors.newFixedThreadPool(COMMIT_CONNECTIONS + ROLLBACK_CONNECTIONS);
		startTimeStamp = System.currentTimeMillis();
		for (int i = 0; i < COMMIT_CONNECTIONS; i++) {
			int finalI = i;
			executorService.submit(() -> {
				System.out.println(GT.tr("Start commit thread {0}", finalI));
				try {
					executeCommitConnections(finalI);
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			});
		}
		for (int i = 0; i < ROLLBACK_CONNECTIONS; i++) {
			int finalI = i;
			executorService.submit(() -> {
				System.out.println(GT.tr("Start rollback thread {0}", finalI));
				try {
					executeRollbackConnections(finalI);
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			});
		}
		executorService.shutdown();
	}

	public static void main(String[] args) throws SQLException {
		System.out.println(
				GT.tr("required params: ip:port db user pwd insert_num(savepoint num) timebeforerollback(seconds) "
						+ "commit_con_num rollback_con_num"));
		if (args.length >= 2) {
			URLstr = "jdbc:opengauss://" + args[0] + "/" + args[1]
					+ "?loggerLevel=OFF&socketTimeoutInConnecting=0&socketTimeout=0";
		}
		if (args.length >= 3) {
			SCHEMANAME = args[2];
		}
		if (args.length >= 4) {
			USERNAME = args[3];
		}
		if (args.length >= 5) {
			PASSWORD = args[4];
		}
		if (args.length >= 6) {
			INSERT_NUM = Integer.parseInt(args[5]);
		}
		if (args.length >= 7) {
			timeBeforeRollback = Integer.parseInt(args[6]) * 1000;
		}
		if (args.length >= 8) {
			COMMIT_CONNECTIONS = Integer.parseInt(args[7]);
		}
		if (args.length >= 9) {
			ROLLBACK_CONNECTIONS = Integer.parseInt(args[8]);
		}
		initTables();
		execute();
	}
}

class DatetimeUtil {

	/**
	 * @return yyyy-MM-dd
	 */
	public static String getNowDate() {
		SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd");
		Date date = new Date();
		return time.format(date);
	}

	/**
	 * @return yyyy-MM-dd HH:mm
	 */
	public static String getTheTimeInMinutes() {
		SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm");
		Date date = new Date();
		return time.format(date);
	}

	/**
	 * @return yyyy-MM-dd HH:mm:ss
	 */
	public static String getTheTimeInSeconds() {
		SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date date = new Date();
		return time.format(date);
	}

	/**
	 * @return yyyy-MM-dd HH:mm:ss.SSS
	 */
	public static String getTheTimeInMilliseconds() {
		SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		Date date = new Date();
		return time.format(date);
	}

}